<!--
  下拉刷新上拉加载组件               zls-2022/05/30
      Attributes
          1、list           列表数据（必填）
          2、pageNum        页数（必填）
          3、total          总页数（必填）
          4、backTop        是否显示返回顶部按钮(默认为true：显示)
          5、hasPullup      是否有上拉加载事件(默认为true：有)

      Events
          1、pullDown       用于修改pageNum = 1,调用接口
          2、pullUp         用于修改pageNum++,调用接口

      Demo==>
        注意：
          正常情况下列表页是需要使用keep-alive缓存的，对于需要做缓存的组件我们必须记录离开列表页时滚动的位置，因此才会有bs、scrollY、scrollYNew属性；onMounted、onActivated、onDeactivated钩子函数；scroll方法！！  （如果列表页不需要做缓存，可省略上述内容！！）

          <template>
            <div class="list">
              <title class="title">头部</title>
              <div class="pulldownup-box">
                <z-pulldownup :page="listOp.page" :total="listOp.total" :list="listOp.list" @pullDown="pullDown" @pullUp="pullUp" @scroll="scroll">
                  <ul class="content">
                    <li v-for="(item, i) of listOp.list" :key="i" @click="goDetail(item)">I am item {{ item }}</li>
                  </ul>
                </z-pulldownup>
              </div>
            </div>
          </template>

          <script>
          export default {
            name: 'list'
          }
          </script>
          <script setup>
          import { reactive, onMounted, onActivated, onDeactivated } from 'vue'
          import { useRouter } from 'vue-router'
          import store from '@/store'
          const router = useRouter()

          // 列表配置项
          const listOp = reactive({
            list: [],
            page: 1,
            total: 0,
            bs: null,
            scrollY: 0,
            scrollYNew: 0
          })

          onMounted(() => {
            store.keepAlive.SET_KEEP_ALIVE('list') // 缓存列表组件
          })
          onActivated(() => {
            listOp.bs.refresh()
            listOp.bs.scrollTo(0, listOp.scrollYNew, 300)
          })
          onDeactivated(() => {
            listOp.scrollYNew = listOp.scrollY
          })

          // 获取数据
          async function getData() {
            setTimeout(() => {
              const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
              listOp.total = 5
              if (listOp.page === 1) {
                listOp.list = []
              }
              listOp.list = [...listOp.list, ...data]
            }, 1500)
          }
          // 下拉刷新
          function pullDown() {
            listOp.page = 1
            getData()
          }
          // 上拉加载
          function pullUp() {
            listOp.page++
            getData()
          }
          // 滚动事件
          function scroll(y, bscroll) {
            listOp.scrollY = y
            listOp.bs = bscroll
          }
          // 跳转到详情页
          function goDetail(item) {
            router.push({
              name: 'listDetail',
              params: item
            })
          }
          </script>

          <style lang="scss" scoped>
          .list {
            height: 100%;
            .title {
              height: 50px;
              border-bottom: 1px solid #efefef;
              @include flex();
            }
            .pulldownup-box {
              height: calc(100% - 100px);
              .content {
                padding: 0 10px;
                > li {
                  padding: 10px;
                  border-bottom: 1px solid #ccc;
                }
              }
            }
          }
          </style>
-->
<template>
  <div class="z-pulldownup" ref="zPulldownup">
    <div class="scroller">
      <div class="pulldown-tips">{{ pulldownTips }}</div>
      <div ref="zContent">
        <slot></slot>
      </div>
      <div class="pullup-tips" v-if="list.length">{{ pullupTips }}</div>
    </div>
    <div class="pulldownup-blank" v-if="!list.length">暂无数据</div>
    <div v-if="backTop && showBackTop" class="backTop" @click="goTop"></div>
  </div>
</template>

<script>
export default { name: 'z-pulldownup' }
</script>
<script setup>
import { ref, onMounted, watch, nextTick } from 'vue'
import BScroll from '@better-scroll/core'
import Pullup from '@better-scroll/pull-up'
import PullDown from '@better-scroll/pull-down'
BScroll.use(Pullup)
BScroll.use(PullDown)
const emit = defineEmits(['pullDown', 'pullUp', 'scroll'])
const props = defineProps({
  list: {
    type: Array,
    default: () => []
  },
  page: {
    type: Number,
    default: 1
  },
  total: {
    type: Number,
    default: 0
  },
  backTop: {
    type: Boolean,
    default: true
  },
  hasPullup: {
    type: Boolean,
    default: true
  }
})
const zPulldownup = ref()
const zContent = ref()
const bscroll = ref(null) // better-scroll实例
const bounceTime = ref(800) // 回弹动画的动画时长
const pulldownTips = ref('') // 提示文字
const pullupTips = ref('') // 提示文字信息
const showBackTop = ref(false) // 是否显示回到顶部按钮

// 提示文本在不同状态下的提示文字不同
const pulldownTipsObj = {
  moving: {
    enter: '下拉刷新...',
    leave: '释放即可刷新...'
  },
  fetching: '刷新中...',
  succeed: '刷新成功'
}

onMounted(() => {
  initBscroll()
})

watch(
  () => props.list,
  () => {
    nextTick(() => {
      finishPulldown()
      props.hasPullup && finishPullup()
    })
  }
)

// 初始化better-scroll实例
function initBscroll() {
  bscroll.value = new BScroll(zPulldownup.value, {
    scrollY: true,
    click: true,
    bounceTime: bounceTime.value,
    useTransition: false,
    disableTouch: false,
    pullUpLoad: { threshold: 0 },
    pullDownRefresh: { threshold: 70, stop: 56 }
  })
  bscroll.value.on('pullingDown', pullingDownHandler)
  // v2.4.0 supported
  bscroll.value.on('enterThreshold', () => {
    pulldownTips.value = pulldownTipsObj.moving.enter
  })
  bscroll.value.on('leaveThreshold', () => {
    pulldownTips.value = pulldownTipsObj.moving.leave
  })

  props.hasPullup && bscroll.value.on('pullingUp', pullingUpHandler)

  bscroll.value.on('scroll', ({ y }) => {
    showBackTop.value = Math.abs(y) >= 300
    emit('scroll', y, bscroll.value)
  })
  // 进入页面，自动执行下拉刷新
  bscroll.value.autoPullDownRefresh()
}

// 下拉事件
function pullingDownHandler() {
  pulldownTips.value = pulldownTipsObj.fetching
  pullupTips.value = ''
  emit('pullDown')
}

// 完成下拉事件后，结束下拉
function finishPulldown() {
  pulldownTips.value = pulldownTipsObj.succeed
  bscroll.value.finishPullDown()
  setTimeout(() => {
    bscroll.value.refresh()
  }, bounceTime.value + 50)
}

// 当距离滚动到底部小于 threshold 阈值时，触发上拉事件。
function pullingUpHandler() {
  if (props.page < props.total) {
    pullupTips.value = '加载中...'
    emit('pullUp') // 向父组件传递上拉加载函数(用于修改page++，调用接口)
  } else {
    // $slots.default()[0].el.offsetHeight 无效！需要在slot标签外套一层div
    pullupTips.value = zContent.value.offsetHeight > zPulldownup.value.offsetHeight ? '-- 我是有底线的 --' : ''
  }
}

// 结束上拉
function finishPullup() {
  bscroll.value.finishPullUp()
  bscroll.value.refresh()
  // pullupTips.value = $slots.default[0].elm.offsetHeight > z-pulldownup.offsetHeight ? '上拉加载更多数据...' : ''
}

// 返回顶部
function goTop() {
  bscroll.value.scrollTo(0, 0, 300)
}
</script>

<style scoped lang="scss">
.z-pulldownup {
  height: 100%;
  position: relative;
  overflow: hidden;
  .pulldown-tips {
    position: absolute;
    width: 100%;
    padding: 20px;
    box-sizing: border-box;
    transform: translateY(-100%) translateZ(0);
    text-align: center;
    color: #999;
  }
  .pullup-tips {
    height: 50px;
    line-height: 50px;
    text-align: center;
    color: #999;
  }
  .pulldownup-blank {
    height: 100%;
    @include flex();
    font-size: 16px;
  }
  .backTop {
    position: absolute;
    right: 10px;
    bottom: 10px;
    width: 30px;
    height: 30px;
    border-radius: 3px;
    background-color: rgba(0, 0, 0, 0.3);
    @include flex();
    &:before,
    &:after {
      content: '';
      position: absolute;
      width: 14px;
      height: 14px;
      border: 2px solid #fff;
      border-left-width: 0;
      border-bottom-width: 0;
      transform: matrix(0.5, -0.5, 0.5, 0.5, 0, 0);
    }
    &:before {
      top: 6px;
    }
    &:after {
      top: 14px;
    }
  }
}
</style>
